/*
 * %W% %E% 		
 */
package com.vinhvt.effiport.algorithm.genetic.bio;

import java.util.*;


import com.vinhvt.effiport.algorithm.genetic.GAsParameters;
import com.vinhvt.effiport.financial.ep.Portfolio;
import com.vinhvt.effiport.financial.ep.mapper.EffiPortGeneMap;
import com.vinhvt.effiport.financial.ep.mapper.EffiPortMapper;
import com.vinhvt.effiport.util.Helper;

public class Generation {	
	private List<Individual> generation = new ArrayList<Individual>();
	
	public void initialize() throws InterruptedException {
		int size = GAsParameters.POPULATION;					
		this.destroy();
		
		for(int i = 0; i < size; i++) {
			Individual individual = new Individual(true);
			this.addIndividual(individual);
		}
	}
	
	public Generation evolve() {
		Generation result = new Generation();
		System.out.println("Before S:" + result.getSize());
		result = select();
//		System.out.println("after sel:\n" + result.toString());
		System.out.println("After S:" + result.getSize());
		result = crossover();
//		System.out.println("after c:" + result.toString());
		System.out.println("After C:" + result.getSize());
		result = mutate();
//		System.out.println("after m:" + result.toString());
		System.out.println("After M:" + result.getSize());
		return result;
		
	}
	
	private void destroy() {
		this.generation.clear();
	}
	
	/**
	 * Gets the best individual.
	 * 
	 * @return the best individual
	 */
	public Individual getBestIndividual() {
		Individual result = null;
		
		int size = this.getSize();
		double maxFitness = 0;
		for(int i = 0; i < size; i++) {
			Individual indiv = this.getIndividualAt(i);
			if(indiv.getFitness() > maxFitness) {
				maxFitness = indiv.getFitness();
				result = indiv;
			}
		}
		
		return result;
	}		
	private void select1() {
		Generation result = new Generation();
		
		int size = this.getSize();
		System.out.println("SS:" + size);
		for(int i = 0; i < size; i++) {
//			if (this.getIndividualAt(i).getFitness() > GAsParameters.MAX_FITNESS*0.2) {
				result.addIndividual(this.getIndividualAt(i));
//			}		
		}	
		System.out.println("SS:" + result.getSize());
		this.destroy();
		for(int i = 0; i < result.getSize(); i++) {
			this.addIndividual(result.getIndividualAt(i));
		}		
	}
	private Generation select() {
		Generation result = new Generation();
		this.select1();
		
		double totalAdjustment = 0.0;
		int size = this.getSize();
		for(int i = 0; i < size; i++) {			
			totalAdjustment += this.getIndividualAt(i).getFitness();
		}
		
		//Calculating incremental probability
		size = this.getSize();
		double[] pp = new double[size];
		for(int i = 0; i < size; i++) {
			Individual ind = this.getIndividualAt(i);
			double ps = ind.getFitness() / totalAdjustment;
			pp[i] = (i == 0) ? ps : pp[i - 1] + ps;
		}
		
		//turn around the roulet wheel n times;
		Random rand = new Random();
		for(int i = 0; i < size ; i++) {
			double randDouble = rand.nextDouble();
			int pos = Helper.GetPosition(randDouble, pp);
			Individual ind = this.getIndividualAt(pos);
			result.addIndividual(ind);
		}
		
		return result;
	}
	
	private Generation mutate() {
		Generation result = this;

        // For each Individuals in current Generation, And for each Bit of Chromosome, we will generate a random number           
        // If it's smaller than Mutation Probability, the Bit at that position of that Individual will be mutated ( 0 -> 1 or 1 - >0)
		int size = this.getSize();
		Random rand = new Random();
		System.out.println("S:" + generation.size());
        for (int i = 0; i < size; i++)
        {
        	double randDouble = rand.nextDouble();
        	if (randDouble < GAsParameters.PM) {
	        	Individual ind = this.getIndividualAt(i);	       
	        	ind.mutate();
	            result.addIndividual(ind);
        	}
        }
        System.out.println("S:" + generation.size());
        return result;
	}

	private Generation crossover() {
		Generation result = this;
		
		Generation selectedIndividuals = new Generation();
		
		// Select some Individuals which take part in the Crossover by probability (pc)
        Random rand = new Random();
        int size = this.getSize();
        double pc = GAsParameters.PC;
        for (int i = 0; i < size; i++) {                
            double randDouble = rand.nextDouble();
            Individual indiv = this.getIndividualAt(i);
            if (randDouble < pc) {
                selectedIndividuals.addIndividual(indiv);                
            }
            else {
                // keep all the individuals that don't take part in the Cross Over in Next Generation
                result.addIndividual(indiv);
            }
            //result.addIndividual(indiv);
        }
        result.addIndividual(this.getBestIndividual());        
        
        if (selectedIndividuals.getSize() % 2 == 1) // number of selected Individuals is ODD so that there is a Single Individual
        { 
            //  In this case, we will add or remove randomly.
            //  We will generate Integer Random in range of 0, N (N = size of group of individuals which are selected)
            //  With an Odd number, we will ADD. Otherwise, the Random Integer is EVEN, we will REMOVE.
            //  Position of Individual which will be ADD or REMOVE is decided by Integer Random.

            int randInt = rand.nextInt(1);                
            if (randInt == 1) {
            	// we will add the Individual at position = (randInt)
            
                // pop an Individual in New Generation that has been created in previous steps.
                randInt = rand.nextInt(result.getSize() - 1);
                Individual addedIndividual = result.removeIndividualAt(randInt);
                selectedIndividuals.addIndividual(addedIndividual);                                                                                   
            }
            else { 
            	// we will also remove the Individual at position = (randInt)
            
                // individual don't take part in Crossover.
                Individual indiv = selectedIndividuals.removeIndividualAt(randInt);

                // We should keep her in next generation
                result.addIndividual(indiv);                    
            }                
        }
        
     // Execute the Crossover Cycle            
        while (selectedIndividuals.getSize() > 1) // at least two individual
        {
            Individual dad = selectedIndividuals.PopIndividual();
            Individual mom = selectedIndividuals.PopIndividual();

            // Single Crossover
            Individual[] offspring = new Individual[2];
            offspring = dad.getOffspringWith(mom);

            // add Offspring into new Generation
            if (offspring[0].getFitness() > 0) result.addIndividual(offspring[0]);
            if (offspring[1].getFitness() > 0) result.addIndividual(offspring[1]);                            
            // Multive Cross

        }        
		return result;
	}

	
	private Individual PopIndividual() {
		Individual result;
        
        int randPosition = new Random().nextInt(this.getSize());
        result = this.removeIndividualAt(randPosition);

        return result;
	}

	public Individual removeIndividualAt(int pos) {
		Individual result = new Individual();
		
        result = this.getIndividualAt(pos);
        this.generation.remove(pos);                            

        return result;
	}

	

	public Individual getIndividualAt(int position) {
		return this.generation.get(position);
	}
	
	public void setIndividualAt(int position, Individual individual) {
		this.generation.set(position, individual);
	}
	
	public void addIndividual(Individual individual) {
		this.generation.add(individual);
	}
	
	public int getSize() {
		return this.generation.size();
	}
	
	
	
	@Override
	public String toString() {
		String result = "";
		
		for(int i = 0; i < this.generation.size(); i++) {
			result += this.getIndividualAt(i).toString();
		}		
		return result;
	}		
	
//	public String toSolution(EffiPortGeneMap geneMap) {
//		String result = "";
//		
//		EffiPortMapper mapper = EffiPortMapper.mapping(geneMap);
//		for(int i = 0; i < this.getSize(); i++) {
//			Individual ind = this.getIndividualAt(i);
//			mapper.map2Solution(ind);
//			Portfolio port = mapper.getPortfolio();
//			result += port.toString();
//		}
//		
//		return result;
//	}
}
